// getUsedValue() version 0.4.0
// Copyright 2009 hax<johnhax@gmail.com>

// You can use and distribute these codes under LGPL v3 license.

var CSSUtil = new function () {
	
	var CSSValueUnitTypes = [
		'unknown', 'number', '%',
		'em', 'ex', 'px',
		'cm', 'mm', 'in', 'pt', 'pc',
		'deg', 'rad', 'grad',
		'ms', 's', 'hz', 'khz',
		'dimension', 'string', 'uri', 'indent',
		'attr', 'counter', 'rect', 'rgbcolor'
	]
	
	function isLengthUnit(unit) {
		return /^(px|em|%|pt|mm|cm|pc|in|ex|rem|vw|vh|vm|ch|gr)$/.test(unit)
	}
	function isAbsoluteLengthUnit(unit) {
		return /^(pt|mm|cm|pc|in)$/.test(unit)
	}
	function isColorUnit(unit) {
		return /^(rgbcolor|#|rgb|rgba|hsl|hsla)$/.test(unit)
	}
	
	function Color(value) {
		if (value == null) return
		if (value instanceof Color) return value
		var c = Color[value]
		if (c) return c
		if (!(this instanceof Color)) return new Color(value)
		if (value.charAt(0) == '#') {
			switch (value.length) {
				case 7:
					this.red = parseInt(value.slice(1, 3), 16)
					this.green = parseInt(value.slice(3, 5), 16)
					this.blue = parseInt(value.slice(5, 7), 16)
					this.alpha = 1
					break
				case 4:
					this.red = parseInt(value.slice(1, 2), 16) * 0x11
					this.green = parseInt(value.slice(2, 3), 16) * 0x11
					this.blue = parseInt(value.slice(3, 4), 16) * 0x11
					this.alpha = 1
					break
				case 9:
					this.alpha = parseInt(value.slice(1, 3), 16) / 0xff
					this.red = parseInt(value.slice(3, 5), 16)
					this.green = parseInt(value.slice(5, 7), 16)
					this.blue = parseInt(value.slice(7, 9), 16)
					break
				default:
					throw Error('Bad color format: ' + value)
			}
		} else switch(value.slice(0, 4)) {
			case 'rgb(':
				var a = /rgb\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*\)/.exec(value)
				this.red = parseFloat(a[1])
				this.green = parseFloat(a[2])
				this.blue = parseFloat(a[3])
				this.alpha = 1
				break
			case 'rgba':
				var a = /rgba\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?[\d.]+)\s*\)/.exec(value)
				this.red = parseInt(a[1], 10)
				this.green = parseInt(a[2], 10)
				this.blue = parseInt(a[3], 10)
				this.alpha = parseFloat(a[4])
				if (this.alpha < 0) this.alpha = 0
				else if (this.alpha > 1) this.alpha = 1
				break
			case 'hsl(':
				throw Error('hsl() has not been implemented')
				break
			case 'hsla':
				throw Error('hsla() has not been implemented')
				break
			case 'cmyk':
				throw Error('cmyk() has not been implemented')
				break
			default:
				// system colors
				this.value = value
		}
	}
	Color.RGB = function (r, g, b) {
		return Color.RGBA(r, g, b, 1)
	}
	Color.RGBA = function (r, g, b, a) {
		var c = new Color()
		c.red = r
		c.green = g
		c.blue = b
		c.alpha = a
		return c
	}
	Color.HSL = function (h, s, l) {
		return Color.HSLA(h, s, l, 1)
	}
	Color.HSLA = function (h, s, l, a) {
		var c = new Color()
		var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s
		var m1 = l * 2 - m2
		c.red = hue2rgb(m1, m2, h + 1/3) * 0xff
		c.green = hue2rgb(m1, m2, h) * 0xff
		c.blue = hue2rgb(m1, m2, h - 1/3) * 0xff
		c.alpha = a
		return c
	}
	function hue2rgb(m1, m2, h) {
		if (h < 0) h++
		else if (h > 1) h--
		if (h * 6 < 1) return m1 + (m2 - m1) * h * 6
		if (h * 2 < 1) return m2
		if (h * 3 < 2) return m1 + (m2 - m1) * (2/3 - h) * 6
		return m1
	}
	Color.prototype.to = function (unit) {
		switch (unit) {
			case 'rgbcolor': return this.toRGBColor()
			case 'rgb': return this.toRGB()
			case 'rgba': return this.toRGBA()
			case '#': return this.toHex()
			case 'hsl': return this.toHSL()
			case 'hsla': return this.toHSLA()
			default: return this.toString()
		}
	}
	Color.prototype.toString = function () {
		if (this.value) return this.value
		switch (this.alpha) { // opera
			case 1:
				var r = this.red.toString(16)
				if (r.length == 1) r = '0' + r
				var g = this.green.toString(16)
				if (g.length == 1) g = '0' + g
				var b = this.blue.toString(16)
				if (b.length == 1) b = '0' + b
				return '#' + r + g + b
			case 0:
				return 'transparent'
			default:
				return 'rgba(' + [this.red, this.green, this.blue, this.alpha].join(', ') + ')'
		}
	}
	Color.prototype.toRGBColor = function () { // cssom
		this.toString = function () {
			if (this.value) return this.value
			if (this.alpha == 1) {
				var r = this.red.toString(16)
				if (r.length == 1) r = '0' + r
				var g = this.green.toString(16)
				if (g.length == 1) g = '0' + g
				var b = this.blue.toString(16)
				if (b.length == 1) b = '0' + b
				return '#' + r + g + b
			} else {
				return 'rgba(' + [this.red, this.green, this.blue, this.alpha].join(', ') + ')'
			}
		}
		this.red = new Number(this.red)
		this.green = new Number(this.green)
		this.blue = new Number(this.blue)
		this.alpha = new Number(this.alpha)
		this.red.getFloatValue = this.green.getFloatValue = this.blue.getFloatValue = 
		this.alpha.getFloatValue = function () { return Number(this) }
		return this
	}
	Color.prototype.toRGB = function () { // webkit
		return this.alpha == 1 ?
			'rgb(' + [this.red, this.green, this.blue].join(', ') + ')' :
			'rgba(' + [this.red, this.green, this.blue, this.alpha].join(', ') + ')'
	}
	Color.prototype.toRGBA = function () {
		return 'rgba(' + [this.red, this.green, this.blue, this.alpha].join(', ') + ')'
	}
	Color.prototype.toHex = function () {
		var s = '#'
		switch (this.alpha) {
			default:
				var a = this.alpha.toString(16)
				s += a.length == 1 ? '0' + a : a
			case 1:
				var r = this.red.toString(16)
				if (r.length == 1) r = '0' + r
				var g = this.green.toString(16)
				if (g.length == 1) g = '0' + g
				var b = this.blue.toString(16)
				if (b.length == 1) b = '0' + b
				return s + r + g + b
			case 0:
				return 'transparent'
		}
	}
	var ColorKeywords = {
		aqua:'#00ff00', 
		black:'#000000',
		blue:'#0000ff',
		fuchsia:'#ff00ff',
		gray:'#808080',
		grey:'#808080',
		green:'#008000',
		lime:'#00ff00', 
		maroon:'#800000',
		navy:'#000080',
		olive:'#808000',
		orange:'#ffa500', // CSS 2.1
		purple:'#800080',
		red:'#ff0000',
		silver:'#c0c0c0',
		teal:'#008080',
		white:'#ffffff',
		yellow:'#ffff00',
		transparent:Color.RGBA(0,0,0,0)
	}
	/// TODO: add other color keywords which IE support
	for (var k in ColorKeywords) {
		if (ColorKeywords.propertyIsEnumerable(k)) {
			Color[k] = Color(ColorKeywords[k])
		}
	}
	
	function CSSProperty(name) {
		if (name instanceof CSSProperty) return name
		var prop = CSSProperty[name]
		if (prop) return prop
		if (!(this instanceof CSSProperty)) return new CSSProperty(name)
		var vendor
		var words = name.split('-')
		if (words.length > 1) {
			var s = words[0]
			if (s == '') vendor = words[1]
			for (var i = 1; i < words.length; i++) {
				var w = words[i]
				s += w.charAt(0).toUpperCase() + w.slice(1)
			}
			this.name = name
			this.domName = s
		} else {
			words = name.split(/(?=[A-Z])/)
			if (words[0] == '') vendor = words[1]
			this.domName = name
			this.name = words.join('-').toLowerCase()
		}
		
		var tags = {}
		if (vendor) tags.extension = vendor
		for (var i = 0; i < words.length; i++) {
			tags[words[i].toLowerCase()] = true
		}
		tags.length = tags.width || tags.height ||
			tags.padding || tags.margin || tags.spacing ||
			this.name.search(/^(left|top|right|bottom|font-size|text-indent|background-position|clip)$/) != -1
		
		this.tags = tags
		
		CSSProperty[this.name] = CSSProperty[this.domName] = this
	}
	CSSProperty.prototype.alias = function (name) {
		return CSSProperty[name] = this
	}
	CSSProperty('float').
		alias('cssFloat').
		alias('styleFloat').
		domName = 'cssFloat'
	
	function isLengthProperty(prop) {
		return CSSProperty(prop).tags.length
	}
	function isColorProperty(prop) {
		return CSSProperty(prop).tags.color
	}
	
	function convertAbsoluteLength(n, u, u2) {
		if (u == u2) return n
		var t = { in_cm:2.54, in_pt:72, cm_mm:10, pc_pt:12 }
		t.in_mm = t.in_cm * t.cm_mm //25.4
		t.in_pc = t.in_pt / t.pc_pt //6
		t.cm_pt = t.in_pt / t.in_cm
		t.cm_pc = t.in_pc / t.in_cm
		t.mm_pt = t.in_pt / t.in_mm
		t.mm_pc = t.in_pc / t.in_mm
		var r
		if (r = t[u + '_' + u2]) return n * r
		if (r = t[u2 + '_' + u]) return n / r
		throw Error('Can not convert: ' + u + ' -> ' + u2)
	}

	function getDPI() {
		if (!getDPI._1in) {
			getDPI._1in = document.createElement('div')
			var s = getDPI._1in.style
			s.margin = s.borderWidth = s.padding = '0'
			s.maxWidth = s.minWidth = s.width = '1in'
		}
		return getDPI._1in.style.pixelWidth
	}
	function calcPixelLength(elt, cssLength) {
		var s
		if (!calcPixelLength._temp) {
			calcPixelLength._temp = document.createElement('div')
			s = calcPixelLength._temp.style
			s.margin = s.borderWidth = s.padding = '0'
			s.display = 'none'
		}
		s = calcPixelLength._temp.style
		s.maxWidth = s.minWidth = s.width = cssLength
		elt.appendChild(calcPixelLength._temp)
		var r = s.pixelWidth
		elt.removeChild(calcPixelLength._temp)
		return r
	}
	function calcFontSize(size) {
		var s
		if (!calcFontSize._temp) {
			calcFontSize._temp = document.createElement('div')
			s = calcFontSize._temp.style
			s.margin = s.borderWidth = s.padding = '0'
			s.maxWidth = s.minWidth = s.width = '1em'
		}
		s = calcFontSize._temp.style
		s.fontSize = size
		return s.pixelWidth
	}
	
	
	function getUsedValue(elt, prop, unit) {
		
		prop = CSSProperty(prop)
		
		var cs, v
		
		if (typeof getComputedStyle == 'function') {
			
			cs = getComputedStyle(elt, null)
			
			if (unit != null) {
				/// TODO: Opera doesn't support getPropertyCSSValue
				var unitType = CSSValueUnitTypes.indexOf(unit)
				if (unitType >= 5 && unitType <= 10)
					return cs.getPropertyCSSValue(prop.name).getFloatValue(unitType)
			}
			
			v = cs[prop.domName]
			if (v === undefined) {
				if (prop.name == 'box-sizing')
					v = cs.MozBoxSizing || cs.WebkitBoxSizing
				return v
			}
			
		} else if (cs = elt.currentStyle) {
			
			v = cs[prop.domName]
			if (v === undefined) {
				switch (prop.name) {
					case 'float':
						return cs['styleFloat']
					case 'box-sizing': 
						return elt.ownerDocument.compatMode == 'CSS1Compat' ?
							'content-box' : 'border-box'
					default: return undefined
				}
			}
			
			if (isLengthUnit(unit) || isLengthProperty(prop)) {
				
				var n, u // number and unit
				
				switch (prop.name) {
					case 'font-size':
						if (v == 'smaller' || v == 'larger') {
							throw Error('Calculation of font relative size has not implemented yet')
						} else if (v.search(/small|medium|large/) != -1) {
							n = calcFontSize(v)
							u = 'px'
						} else if (v.slice(-2) == 'em') {
							n = calcPixelLength(elt.parentNode, v)
							u = 'px'
						} else if (v.slice(-1) == '%') {
							n = calcPixelLength(elt.parentNode, 
								v.slice(0, -1) + 'em') / 100
							u = 'px'
						}
						break
					case 'border-left-width': case 'border-top-width':
					case 'border-right-width': case 'border-bottom-width':
						var bs = cs[prop.domName.replace('Width', 'Style')]
						if (bs == 'none')
							return unit == null ? '0' : 0
						switch (v) {
							case 'thin':
							case 'medium':
	// IE weird: td/th border width is 'medium' by default!
	// Is there any method to distingiush whether a medium value
	// is from specified value or initial value?
	// initial value for td/th: medium inset
								var tagName = elt.tagName.toLowerCase()
								if ((tagName == 'td' || tagName == 'th') && bs == 'inset') {
									n = 1
									u = 'px'
								}
								break
							case 'thick':
								throw Error('border:' + v + ' ' + bs + ' has not implemented yet')
						}
						break
					case 'width':
						if (v == 'auto' || v.slice(-1) == '%') {
							n = elt.offsetWidth
							switch (getUsedValue(elt, 'box-sizing')) {
								case 'content-box':
									n -= (
										getUsedValue(elt, 'padding-left', 'px') +
										getUsedValue(elt, 'padding-right', 'px')
									)
								case 'padding-box':
									n -= (
										getUsedValue(elt, 'border-left-width', 'px') +
										getUsedValue(elt, 'border-right-width', 'px')
									)
								default:
							}
							u = 'px'
							v = n + u
						}
						break
					case 'height':
						if (v == 'auto' || v.slice(-1) == '%') {
							n = elt.offsetHeight
							switch (getUsedValue(elt, 'box-sizing')) {
								case 'content-box':
									n -= (
										getUsedValue(elt, 'padding-top', 'px') +
										getUsedValue(elt, 'padding-bottom', 'px')
									)
								case 'padding-box':
									n -= (
										getUsedValue(elt, 'border-top-width', 'px') +
										getUsedValue(elt, 'border-bottom-width', 'px')
									)
								default:
							}
							u = 'px'
							v = n + u
						}
						break
					case 'left': case 'right': case 'top': case 'bottom':
						if (v == 'auto') return 'auto' // static position should return 'auto'?
						//n = elt['offset' + prop.name.charAt(0).toUpperCase() + prop.name.slice(1)]
						//u = 'px'
						break
				}
				
				if (n == null) {
					var a = /^([+-]?([0-9]+|[0-9]*[.][0-9]+))(em|ex|px|in|cm|mm|pt|pc|%)?$/.exec(v)
					if (a == null) throw Error('Unknown length format: ' + v + ' for ' + prop.name)
					n = parseFloat(a[1]), u = a[3]
				}
				
				if (u == '%') {
					n = elt.offsetParent.clientWidth * n / 100
					u = 'px'
					//throw Error(prop.name + ':' + v + ', Percentage value calculation has not implemented yet')
				}
				if (u == unit) {
					return n
				} else if (u == 'px') {
					if (unit == null) return v
					if (isAbsoluteLengthUnit(unit))
						return convertAbsoluteLength(n / getDPI(), 'in', unit)
				} else if (isAbsoluteLengthUnit(u)) {
					if (isAbsoluteLengthUnit(unit))
						return convertAbsoluteLength(n, u, unit)
					if (unit == null) return convertAbsoluteLength(n, u, 'in') * getDPI() + 'px'
					if (unit == 'px') return convertAbsoluteLength(n, u, 'in') * getDPI()
				}
				var pixelProp = {
					width:'pixelWidth',height:'pixelHeight',
					top:'pixelTop',bottom:'pixelBottom',
					left:'pixelLeft',right:'pixelRight'
				}[prop.domName]
				if (pixelProp) {
					if (elt.runtimeStyle[prop.domName]) {
						n = elt.runtimeStyle[pixelProp]
					} else {
						elt.runtimeStyle[prop.domName] = v
						n = elt.runtimeStyle[pixelProp]
						elt.runtimeStyle[prop.domName] = null
					}
					u = 'px'
					if (unit == null) return n + 'px'
					if (unit == 'px') return n
				}
				if (u == 'ex') {
					throw Error(prop.name + ':' + v + ', ex value calculation has not implemented yet')
				}
				if (u == 'em') {
					n *= getUsedValue(elt, 'font-size', 'px')
					if (unit == 'px') return n
					if (unit == null) return n + 'px'
					throw Error('em -> ' + unit + ' convertion has not implemented yet')
				}
				if (unit == 'em') {
					return prop.name != 'font-size' ?
						n / getUsedValue(elt, 'font-size', u) :
						n / getUsedValue(elt.parentNode, 'font-size', u)
				}
				throw Error(prop.name + ':' + v + '(' + n + u + ')' + ' -> ?' + unit)
			
			}
			
		} else {
			throw Error('Neither getComputedStyle nor currentStyle...')
		}
		
		if (isColorUnit(unit) || isColorProperty(prop)) {
			return Color(v).to(unit)
		}
		
		return v
		
	}
	
	this.getUsedValue = getUsedValue
	this.Color = Color
}

var getUsedValue = CSSUtil.getUsedValue
var Color = CSSUtil.Color